/*
 * Copyright (C) 2015 tyorikan
 * Copyright (C) 2015 The Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.tyorikan.voicerecordingvisualizer;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;

import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.render.BlendMode;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

/**
 * A class that draws visualizations of data received from {@link RecordingSampler}
 *
 * Created by tyorikan on 2015/06/08.
 */
public class VisualizerView extends DirectionalLayout implements Component.DrawTask {
    private static final int DEFAULT_NUM_COLUMNS = 20;
    private static final int RENDAR_RANGE_TOP = 0;
    private static final int RENDAR_RANGE_BOTTOM = 1;
    private static final int RENDAR_RANGE_TOP_BOTTOM = 2;
    private static final int BAR = 1;
    private static final int PIXEL = 2;
    private static final int FADE = 4;
    private static final int NOT_FADE = 0;

    private int mNumColumns;
    private Color mRenderColor;
    private int mFade;
    private int mType;
    private int mRenderRange;

    private int mBaseY;

    private Canvas mCanvas;
    private PixelMap mCanvasBitmap;
    private final Rect mRect = new Rect();
    private final Paint mPaint = new Paint();
    private final Paint mFadePaint = new Paint();

    private float mColumnWidth;
    private float mSpace;

    public VisualizerView(Context context, AttrSet attrs) {
        super(context, attrs);

        init(attrs);
        addDrawTask(this::onDraw);
        mPaint.setColor(mRenderColor);
        mFadePaint.setColor(new Color(Color.argb(138, 255, 255, 255)));
    }

    private void init(AttrSet attrs) {
        mNumColumns = attrs.getAttr("numColumns").isPresent() ? attrs.getAttr("numColumns")
                .get().getIntegerValue() : DEFAULT_NUM_COLUMNS;
        mRenderColor = attrs.getAttr("renderColor").isPresent() ? attrs.getAttr("renderColor")
                .get().getColorValue() : Color.WHITE;
        String tempFade =  attrs.getAttr("fade").isPresent() ? attrs.getAttr("fade")
                .get().getStringValue() : "NOT_FADE";
        if ("fade".equals(tempFade)) {
            mFade = 4;
        } else {
            mFade = 0;
        }
        String tempType =  attrs.getAttr("renderType").isPresent() ? attrs.getAttr("renderType")
                .get().getStringValue() : "bar";
        if ("bar".equals(tempType)) {
            mType = 1;
        } else if ("pixel".equals(tempType)) {
            mType = 2;
        } else {
            mType = 1;
        }
        String tempRange =  attrs.getAttr("renderRange").isPresent() ? attrs.getAttr("renderRange")
                .get().getStringValue() : "top";
        if ("top".equals(tempRange)) {
            mRenderRange = 0;
        } else if ("bottom".equals(tempRange)) {
            mRenderRange = 1;
        } else if ("both".equals(tempRange)) {
            mRenderRange = 2;
        } else {
            mRenderRange = 0;
        }
    }

    /**
     * setBaseY
     *
     * @param baseY center Y position of visualizer
     */
    public void setBaseY(int baseY) {
        mBaseY = baseY;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        // Create canvas once we're ready to draw
        mRect.set(0, 0, getWidth(), getHeight());

        if (mCanvasBitmap == null) {
            PixelMap.InitializationOptions opts = new PixelMap.InitializationOptions();
            opts.size = new Size(getWidth(), getHeight());
            opts.pixelFormat = PixelFormat.ARGB_8888;
            mCanvasBitmap = PixelMap.create(opts);
        }

        if (mCanvas == null) {
            mCanvas = new Canvas(new Texture(mCanvasBitmap));
        }

        if (mNumColumns > getWidth()) {
            mNumColumns = DEFAULT_NUM_COLUMNS;
        }
        mColumnWidth = (float) getWidth() / (float) mNumColumns;
        mSpace = mColumnWidth / 8f;

        if (mBaseY == 0) {
            mBaseY = getHeight() / 2;
        }

        PixelMapHolder pixelMapHolder = new PixelMapHolder(mCanvasBitmap);
        canvas.drawPixelMapHolder(pixelMapHolder, 0, 0, mPaint);
    }

    /**
     * receive volume from {@link RecordingSampler}
     *
     * @param volume volume from mic input
     */
    protected void receive(final int volume) {
        new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
            @Override
            public void run() {
                if (mCanvas == null) {
                    return;
                }

                if (volume == 0) {
                    mCanvas.drawColor(Color.TRANSPARENT.getValue(), Canvas.PorterDuffMode.CLEAR);
                } else if ((mFade == FADE)) {
                    mFadePaint.setBlendMode(BlendMode.DST_IN);
                    mCanvas.drawPaint(mFadePaint);
                } else {
                    mCanvas.drawColor(Color.TRANSPARENT.getValue(), Canvas.PorterDuffMode.CLEAR);
                }
                if ((mType == BAR)) {
                    drawBar(volume);
                }
                if ((mType == PIXEL)) {
                    drawPixel(volume);
                }
                invalidate();
            }
        });
    }

    private void drawBar(int volume) {
        for (int i = 0; i < mNumColumns; i++) {
            float height = getRandomHeight(volume);
            float left = i * mColumnWidth + mSpace;
            float right = (i + 1) * mColumnWidth - mSpace;

            RectFloat rect = createRectF(left, right, height);
            mCanvas.drawRect(rect, mPaint);
        }
    }

    private void drawPixel(int volume) {
        for (int i = 0; i < mNumColumns; i++) {
            float height = getRandomHeight(volume);
            float left = i * mColumnWidth + mSpace;
            float right = (i + 1) * mColumnWidth - mSpace;

            int drawCount = (int) (height / (right - left));
            if (drawCount == 0) {
                drawCount = 1;
            }
            float drawHeight = height / drawCount;

            // draw each pixel
            for (int j = 0; j < drawCount; j++) {
                float top;
                float bottom;
                RectFloat rect;

                switch (mRenderRange) {
                    case RENDAR_RANGE_TOP:
                        bottom = mBaseY - (drawHeight * j);
                        top = bottom - drawHeight + mSpace;
                        rect = new RectFloat(left, top, right, bottom);
                        break;

                    case RENDAR_RANGE_BOTTOM:
                        top = mBaseY + (drawHeight * j);
                        bottom = top + drawHeight - mSpace;
                        rect = new RectFloat(left, top, right, bottom);
                        break;

                    case RENDAR_RANGE_TOP_BOTTOM:
                        bottom = mBaseY - (height / 2) + (drawHeight * j);
                        top = bottom - drawHeight + mSpace;
                        rect = new RectFloat(left, top, right, bottom);
                        break;

                    default:
                        return;
                }
                mCanvas.drawRect(rect, mPaint);
            }
        }
    }

    private float getRandomHeight(int volume) {
        double randomVolume = Math.random() * volume + 1;
        float height = getHeight();
        switch (mRenderRange) {
            case RENDAR_RANGE_TOP:
                height = mBaseY;
                break;
            case RENDAR_RANGE_BOTTOM:
                height = (getHeight() - mBaseY);
                break;
            case RENDAR_RANGE_TOP_BOTTOM:
                height = getHeight();
                break;
        }
        return (height / 60f) * (float) randomVolume;
    }

    private RectFloat createRectF(float left, float right, float height) {
        switch (mRenderRange) {
            case RENDAR_RANGE_TOP:
                return new RectFloat(left, mBaseY - height, right, mBaseY);
            case RENDAR_RANGE_BOTTOM:
                return new RectFloat(left, mBaseY, right, mBaseY + height);
            case RENDAR_RANGE_TOP_BOTTOM:
                return new RectFloat(left, mBaseY - height, right, mBaseY + height);
            default:
                return new RectFloat(left, mBaseY - height, right, mBaseY);
        }
    }

    /**
     * visualizer type
     */
    public enum Type {
        BAR(0x1), PIXEL(0x2), FADE(0x4);

        private final int mFlag;

        Type(int flag) {
            mFlag = flag;
        }

        /**
         * getFlag
         *
         * @return flag
         */
        public int getFlag() {
            return mFlag;
        }
    }
}
